package edu.siu.math.egut.functions;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Collections;
import java.util.Map;
import java.util.Set;

import edu.siu.math.egut.egut.Algorithms;
import edu.siu.math.egut.lie.AbstractEgutFunction;
import edu.siu.math.egut.lie.LiEGroup;
import edu.siu.math.egut.lie.LiEMatrix;
import edu.siu.math.egut.lie.LiEText;
import edu.siu.math.egut.lie.LiEType;
import edu.siu.math.egut.lie.LiEVector;
import edu.siu.math.egut.util.ExceptionalGroup;

/**
 * Represents a parabolic subgroup of a root system. Can be accessed from the
 * interpreter with the following syntax: egutGetParabolicSubgroup(vec weight,
 * grp group).
 * 
 * @author Joe Pleso
 *
 */
public final class GetParabolicSubgroup extends AbstractEgutFunction {

    private  String groupName;
    private  ExceptionalGroup group;
    private  int[] weights;
    private  int[] type;
    private  String cartanType;
    
    private  List<Set<Set<int[]>>> parts;
    //private final Map<Set<byte[]>,byte[]> maximalRoots;
    private  Map<Set<int[]>,int[]> weightOfGraded;
    
    /**
     * Generates a new parabolic subgroup based on the
     * specified group and list of weights.
     * 
     * @param g the parent root system
     * @param w the list of weights corresponding to the
     * simple roots
  
     */
    public void gps (
	   
	    ExceptionalGroup g, int[] w) {
	
	group = g;
	groupName = group.getClass().getSimpleName();
	weights = w;
	if (w.length != g.getRank())
	    throw new IllegalArgumentException();
	parts = new ArrayList<Set<Set<int[]>>>();
	Set<int[]> next = new HashSet<int[]>();
	int max = determineMaximumDepth();
	int[][] roots = g.getPositiveRoots();
	
	for (int i = 1; i <= max; i++){
	    next.clear();
	    Set<Set<int[]>> depth = new HashSet<Set<int[]>>(); 
	    for (int j = 0; j < roots.length; j++){
		if (getDepth(roots[j]) == i)
		    next.add(roots[j]);
	    }
	    while (!next.isEmpty()){
		Set<int[]> part = new HashSet<int[]>();
		Iterator<int[]> iter = next.iterator();
		int[] test = iter.next(); 
		part.add(test);
		
		for (iter.remove();iter.hasNext();){
		    int[] j = iter.next();
		    if (isSamePart(test,j)){
			part.add(j);
			iter.remove();
		    }
		}
		depth.add(part);
	    }
	    parts.add(depth);
	}
	
	//Test to see if any of the weights are bigger than 1
	//I don't know how to handle these cases yet so I just
	//Throw an exception
	for (int i = 0; i < weights.length; i++)
	    if (weights[i] != 0 && weights[i] != 1)
		throw new IllegalArgumentException("Does not handle weights" +
				" larger than 1.");
	
	
	//Calculate simple roots
	int rank = group.getRank() - Algorithms.getWeight(
		weights);
	
	int[][] simpleRoots = new int[rank][group.getRank()];
	
	int j = 0;
	for (int i = 0; i < rank; i++){
	    for (;;j++)
		if (weights[j] == 0){
		    simpleRoots[i][j] = 1;
		    j++;
		    break;
		}
		    
	}
	
	
	//Calculate cartan type
	String lieM = new LiEMatrix(simpleRoots).toString();
	String lieResult;
	//try {
	  
	lieResult = Algorithms.runLiECommand(
		"setdefault " + groupName + 
	    	";m=" + lieM +
	    	";Cartan_type(m)")[0];
	    
	cartanType = lieResult.trim();
	
	type = new int[cartanType.length() / 2]; 
	
	for (int i = 1; i < cartanType.length(); i += 2){
	
	    type[i / 2] = Character.digit(cartanType.charAt(i),10);
		
	    //System.out.println(lieResult);//for debug
		
	}
	//} catch (IOException e) {
	    
	  //  e.printStackTrace();
	//} catch (InterruptedException e) {
	    
	  //  e.printStackTrace();
//	}
	//Now find the maximal root for each part
	//maximalRoots = new HashMap<Set<byte[]>,byte[]>();
	weightOfGraded = new HashMap<Set<int[]>, int[]>();
	for (Set<Set<int[]>> s : parts){
	    for (Set<int[]> t : s){
		int[] maxRoot = null;
		for (int[] root : t){
		    if (maxRoot == null || 
			    Algorithms.getWeight(root) 
			    > Algorithms.getWeight(maxRoot))
			maxRoot = root;
		        
		}
		//maximalRoots.put(t, maxRoot);if we really need this?
		String lieV = new LiEVector(maxRoot).toString();
		
		
		//try {
		    lieResult = Algorithms.runLiECommand(
		    	"setdefault " + groupName + 
		    	";m=" + lieM +
		    	";r=res_mat(m);v=" + lieV +
		    	";v*Cartan*r")[0];
		//} catch (IOException e) {

		  //  e.printStackTrace();
		//} catch (InterruptedException e) {

		  //  e.printStackTrace();
		//}
		weightOfGraded.put(t,
			(int[]) new LiEVector(lieResult).getJavaObject());
	    }
	}
	//Now that we have the maximal root for each
	//graded part of the unipotent part radical
	//we can compute the weight of the levi

	
    }
    


    /**
     * Returns a string representing this parabolic subgroup
     * 
     * @see java.lang.Object#toString()
     */
    
    public String toString(){
	StringBuffer sb = new StringBuffer();
	List<Iterator<int[]>> iter = new ArrayList<Iterator<int[]>>();
	List<Integer> lengths = new ArrayList<Integer>();
	List<Set<int[]>> graded = new ArrayList<Set<int[]>>();
	int total;
	int space;
	int numPerPart;
	for (Set<Set<int[]>> i : parts){
	    space = 80 - ((i.size()-1) * 8);
	    numPerPart = space / (group.getRank() + 1); 
	    
	    total = 0;
	    lengths.clear();
	    iter.clear();
	    graded.clear();
	    for (Set<int[]> j : i){
		iter.add(j.iterator());
		graded.add(j);
		lengths.add(j.size());
		total += j.size();
	    }
	    boolean done = false;
	    //sb.insert(0,'\n');
	    Iterator<Integer> lIter = lengths.iterator(); 
		for (Set<int[]> j : graded){
		    sb.insert(0, '\t');
		    int length = 
			(numPerPart / (total / lIter.next())) - iter.size(); 
		    if ( length <= 0 )
			//throw new RuntimeException(); //For testing
//for production    	
			length = 1;
		    for (int k = 0; k < length; k++){
			if (k==0){
			    sb.insert(
				    0,
				    asType(weightOfGraded.get(j),
					    group.getRank() + 1));
			    
			}else{
			    
			    for (int l = 0; l < (group.getRank() + 1); l++)
				sb.insert(0, ' ');
			}
		    }
		    
			
		}
	    while (!done){
		done = true;
		sb.insert(0,'\n');
	 lIter = lengths.iterator(); 
		
		for (Iterator <int[]> j : iter){
		    sb.insert(0, '\t');
		    int length = 
			(numPerPart / (total / lIter.next())) - iter.size(); 
		    if ( length <= 0 )
			//throw new RuntimeException(); //For testing
//for production    	
			length = 1;
		    for (int k = 0; k < length; k++){
			if (j.hasNext()){
			    sb.insert(
				    0,
			  (group.toString(j.next()) + ' ').substring(1));
			
			    done = false;
			}else{
			    
			    for (int l = 0; l < (group.getRank() + 1); l++)
				sb.insert(0, ' ');
			}
		    }
		    
			
		}
		
	    }
	   
		
		
	    
	}
	sb.append('\n');sb.append('\n');
	sb.append(cartanType);
	sb.append('\n');
	return sb.toString();
    }
    
    private String asType(int[] bs, int size) {
	char[] c = new char[size];
	Arrays.fill(c, ' ');
	
	int count = 0;
	c[0] = '(';
	for (int i = 0; i < type.length - 1; i++){
	    
	    for (int j = 0 ;j < type[i];j++){
		c[count + i + 1] = Integer.toString(bs[count]).charAt(0);
		count++;
	    }
	    
	}
	c[count + type.length - 1] = ')';
	return new String (c);
    }



    private int determineMaximumDepth (){
	
	int[][]roots = group.getPositiveRoots();
	List<Integer> results = new ArrayList<Integer>();
	for (int i = 0; i < roots.length; i++){
	    results.add(getDepth(roots[i]));
	}
	return Collections.max((results));
    }



    private boolean isSamePart (int[] a, int[] b){
	for (int i = 0; i < weights.length; i++)
	    if (weights[i] * a[i] != weights[i] * b[i])
		return false;
	return true;
    }
    
    private int getDepth (int[] root){
	int result = 0;
	for (int i = 0; i < weights.length; i++)
	    result += root[i] * weights[i];
	return result;
    }
    
    



	
	public LiEType[] argList() {
	    LiEType[] result = new LiEType[2];
	    result[0]=LiEType.VEC;
	    result[1]=LiEType.GRP;
	    return result;
	}



	
	public void run() {
	    String [] args = this.getRawData();
	    //Get the arguments from the interpreter
	    
	    LiEVector lieV = new LiEVector(args[0]);
	    int[] root = (int[]) lieV.getJavaObject();
	    //Get the root to check
	    
	    LiEGroup lieG = new LiEGroup(args[1]);
	    ExceptionalGroup rs = (ExceptionalGroup)lieG.getJavaObject();
	    //Get the group
	    
	    
	    if (rs == null){//Make sure that we really have a group
		args[0] = Algorithms.LIE_QUOTE
		+ " is not an exceptional group." +
		Algorithms.LIE_QUOTE;
		this.setData(args);
		return;
	    }
	    
	    if (root.length != rs.getRank()){
		    args[0] = new LiEText("Specified vector did not match the" +
		    		" group.").toString();
		    this.setData(args);
		    return;
		}
		    
		
		gps (rs,root);
		
		//Output
		args[0] = new LiEText(this.toString()).toString();
		this.setData(args);
		return;
	    
	}
    
}
